package com.tonyodev.fetch2core;

import ohos.agp.utils.TextTool;
import ohos.app.Context;
import ohos.utils.Pair;
import ohos.utils.net.Uri;

import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.math.BigInteger;
import java.net.CookieManager;
import java.net.CookiePolicy;
import java.net.HttpURLConnection;
import java.nio.file.Files;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;

/**
 * 工具类
 *
 * @since 2021-05-20
 */
public class FetchCoreUtils {
    /**
     * GET_REQUEST_METHOD
     */
    public static final String GET_REQUEST_METHOD = "GET";
    /**
     * HEAD_REQUEST_METHOD
     */
    public static final String HEAD_REQUEST_METHOD = "HEAD";
    /**
     * HEADER_ACCEPT_RANGE
     */
    public static final String HEADER_ACCEPT_RANGE = "Accept-Ranges";
    /**
     * HEADER_ACCEPT_RANGE_LEGACY
     */
    public static final String HEADER_ACCEPT_RANGE_LEGACY = "accept-ranges";
    /**
     * HEADER_ACCEPT_RANGE_COMPAT
     */
    public static final String HEADER_ACCEPT_RANGE_COMPAT = "AcceptRanges";
    /**
     * HEADER_CONTENT_LENGTH
     */
    public static final String HEADER_CONTENT_LENGTH = "content-length";
    /**
     * HEADER_CONTENT_LENGTH_LEGACY
     */
    public static final String HEADER_CONTENT_LENGTH_LEGACY = "Content-Length";
    /**
     * HEADER_CONTENT_LENGTH_COMPAT
     */
    public static final String HEADER_CONTENT_LENGTH_COMPAT = "ContentLength";
    /**
     * HEADER_TRANSFER_ENCODING
     */
    public static final String HEADER_TRANSFER_ENCODING = "Transfer-Encoding";
    /**
     * HEADER_TRANSFER_LEGACY
     */
    public static final String HEADER_TRANSFER_LEGACY = "transfer-encoding";
    /**
     * HEADER_TRANSFER_ENCODING_COMPAT
     */
    public static final String HEADER_TRANSFER_ENCODING_COMPAT = "TransferEncoding";
    /**
     * HEADER_CONTENT_RANGE
     */
    public static final String HEADER_CONTENT_RANGE = "Content-Range";
    /**
     * HEADER_CONTENT_RANGE_LEGACY
     */
    public static final String HEADER_CONTENT_RANGE_LEGACY = "content-range";
    /**
     * HEADER_CONTENT_RANGE_COMPAT
     */
    public static final String HEADER_CONTENT_RANGE_COMPAT = "ContentRange";

    private static final String REFERER_DEFAULT = "https://developer.huawei.com/";

    /**
     * 计算下载进度
     *
     * @param downloaded 进度
     * @param total 总进度
     * @return progress
     */
    public static final int calculateProgress(long downloaded, long total) {
        if (total < 1L) {
            return -1;
        }
        if (downloaded < 1) {
            return 0;
        }
        if (downloaded >= total) {
            return 100;
        } else {
            double v = (double) downloaded / (double) total * (double) 100;
            return Double.valueOf(v).intValue();
        }
    }

    /**
     * 计算剩余下载时间
     *
     * @param downloadedBytes 下载长度
     * @param totalBytes 总长度
     * @param downloadedBytesPerSecond 每秒下载字节数
     * @return 剩余时间
     */
    public static final long calculateEstimatedTimeRemainingInMilliseconds(long downloadedBytes, long totalBytes, long downloadedBytesPerSecond) {
        long valueByte;
        if (totalBytes < 1L) {
            valueByte = -1L;
        } else if (downloadedBytes < 1L) {
            valueByte = -1L;
        } else {
            if (downloadedBytesPerSecond >= 1L) {
                double seconds = (double) (totalBytes - downloadedBytes) / (double) downloadedBytesPerSecond;
                return (long) Math.abs(Math.ceil(seconds)) * 1000;
            }
            valueByte = -1L;
        }
        return valueByte;
    }

    /**
     * 间隔时间
     *
     * @param nanoStartTime 开始时间
     * @param nanoStopTime 结束时间
     * @param progressIntervalMilliseconds 进度间隔毫秒
     * @return 是否间隔
     */
    public static final boolean hasIntervalTimeElapsed(long nanoStartTime, long nanoStopTime, long progressIntervalMilliseconds) {
        return TimeUnit.NANOSECONDS.toMillis(nanoStopTime - nanoStartTime) >= progressIntervalMilliseconds;
    }

    /**
     * 计算uniqueId
     *
     * @param url 链接
     * @param file 文件
     * @return uniqueId
     */
    public static final int getUniqueId(@NotNull String url, @NotNull String file) {
        Intrinsics.checkParameterIsNotNull(url, "url");
        Intrinsics.checkParameterIsNotNull(file, "file");
        return url.hashCode() * 31 + file.hashCode();
    }

    /**
     * 如果原始文件存在获取增量文件
     *
     * @param originalPath 原始地址
     * @return 文件
     * @throws IOException read fail
     */
    public static final File getIncrementedFileIfOriginalExists(@NotNull String originalPath) throws IOException {
        File file = new File(originalPath);
        int counter = 0;
        if (file.exists()) {
            String parentPath = file.getParent() + '/';
            String name = file.getName();
            int index = name.lastIndexOf('.');
            String extension = index == -1 ? "" : name.substring(index + 1, file.getName().length());
            String fileName = getNameWithoutExtension(file.getName());
            while (file.exists()) {
                ++counter;
                String newFileName = fileName + " (" + counter + ')';
                file = new File(parentPath + newFileName + '.' + extension);
            }
        }
        createFile(file);
        return file;
    }

    /**
     * 创建文件
     *
     * @param file 文件
     * @throws IOException io fail
     * @throws FileNotFoundException file not found
     */
    public static void createFile(@NotNull File file) throws IOException {
        Intrinsics.checkParameterIsNotNull(file, "file");
        if (!file.exists()) {
            if (file.getParentFile() != null && !file.getParentFile().exists()) {
                if (file.getParentFile().mkdirs()) {
                    if (!file.createNewFile()) {
                        throw new FileNotFoundException(file + " " + FetchErrorStrings.FILE_NOT_FOUND);
                    }
                } else {
                    throw new FileNotFoundException(file + " " + FetchErrorStrings.FILE_NOT_FOUND);
                }
            } else if (!file.createNewFile()) {
                throw new FileNotFoundException(file + " " + FetchErrorStrings.FILE_NOT_FOUND);
            }
        }
    }

    /**
     * 获取临时文件地址
     *
     * @param context 上下文
     * @return 地址
     */
    public static final String getFileTempDir(@NotNull Context context) {
        return context.getFilesDir().getAbsolutePath() + "/_fetchData/temp";
    }

    /**
     * 获取文件，如果没有就创建
     *
     * @param filePath 文件地址
     * @return 文件
     * @throws IOException io fail
     */
    public static final File getFile(@NotNull String filePath) throws IOException {
        Intrinsics.checkParameterIsNotNull(filePath, "filePath");
        File file = new File(filePath);
        boolean create;
        if (!file.exists()) {
            if (file.getParentFile() != null && !file.getParentFile().exists()) {
                if (file.getParentFile().mkdirs()) {
                    create = file.createNewFile();
                }
            } else {
                create = file.createNewFile();
            }
        }
        return file;
    }

    /**
     * 写入数据到文件
     *
     * @param filePath 文件地址
     * @param data 数据
     * @throws IOException io fail
     */
    public static final void writeLongToFile(@NotNull String filePath, long data) throws IOException {
        Intrinsics.checkParameterIsNotNull(filePath, "filePath");
        File file = getFile(filePath);
        if (file.exists()) {
            RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
            try {
                randomAccessFile.seek(0L);
                randomAccessFile.setLength(0L);
                randomAccessFile.writeLong(data);
            } catch (Exception var13) {
            } finally {
                try {
                    randomAccessFile.close();
                } catch (Exception var12) {
                }
            }
        }
    }

    /**
     * 获取文件数据
     *
     * @param filePath 文件地址
     * @return 文件数据
     * @throws IOException io fail
     */
    public static final Long getLongDataFromFile(@NotNull String filePath) throws IOException {
        Intrinsics.checkParameterIsNotNull(filePath, "filePath");
        File file = getFile(filePath);
        Long data = null;
        if (file.exists()) {
            RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
            try {
                data = randomAccessFile.readLong();
            } catch (Exception e) {
            } finally {
                try {
                    randomAccessFile.close();
                } catch (Exception e) {
                }
            }
        }
        return data;
    }

    /**
     * 是否fetch服务文件
     *
     * @param url 链接
     * @return 是否fetch 服务链接
     */
    public static final Boolean isFetchFileServerUrl(String url) {
        try {
            return url.startsWith("fetchlocal://")
                && !getFetchFileServerHostAddress(url).isEmpty()
                && getFetchFileServerPort(url) > -1;
        } catch (Exception e) {
            return false;
        }
    }

    /**
     * 获取fetch 文件服务端口
     *
     * @param url 链接
     * @return 端口号
     * @throws NullPointerException null
     */
    public static final int getFetchFileServerPort(@NotNull String url) {
        int colonIndex = url.lastIndexOf(':');
        int length = url.length();
        String modifiedUrl = url.substring(colonIndex + 1, length);
        if (modifiedUrl == null) {
            throw new NullPointerException("null cannot be cast to non-null type java.lang.String");
        }
        int firstSeparatorIndex = modifiedUrl.indexOf('/');
        if (firstSeparatorIndex == -1) {
            return Integer.parseInt(modifiedUrl);
        } else {
            return Integer.parseInt(modifiedUrl.substring(0, firstSeparatorIndex));
        }
    }

    /**
     * 获取fetch 文件服务地址
     *
     * @param url 链接
     * @return 本地地址
     */
    public static final String getFetchFileServerHostAddress(String url) {
        Intrinsics.checkParameterIsNotNull(url, "url");
        int firstIndexOfDoubleSep = url.indexOf("//");
        int colonIndex = url.lastIndexOf(':');
        return url.substring(firstIndexOfDoubleSep + 2, colonIndex);
    }

    /**
     * 获取文件资源
     *
     * @param url 链接
     * @return 文件资源
     */
    public static final String getFileResourceIdFromUrl(String url) {
        Intrinsics.checkParameterIsNotNull(url, "url");
        Uri uri = Uri.parse(url);
        if (uri != null && !TextTool.isNullOrEmpty(uri.getLastPath())) {
            return uri.getLastPath();
        }
        return "-1";
    }

    /**
     * 根据request 获取文件范围
     *
     * @param range 范围
     * @return Pair
     */
    //eg: bytes=10-
    public static final Pair<Long, Long> getRangeForFetchFileServerRequest(@NotNull String range) {
        int firstIndex = range.lastIndexOf('=');
        int lastIndex = range.lastIndexOf('-');
        long start = Long.parseLong(range.substring(firstIndex + 1, lastIndex));
        int length = range.length();
        long end;
        try {
            end = Long.parseLong(range.substring(lastIndex + 1, length));
        } catch (Exception e) {
            end = -1L;
        }
        return new Pair(start, end);
    }

    /**
     * 获取MD5
     *
     * @param bytes 字节
     * @return md5
     */
    public static final String getMd5String(@NotNull byte[] bytes) {
        return getMd5String(bytes, 0, bytes.length);
    }

    /**
     * 获取MD5
     *
     * @param bytes 字节
     * @param start 开始位置
     * @param length 结束位置
     * @return md5
     */
    public static final String getMd5String(@NotNull byte[] bytes, int start, int length) {
        Intrinsics.checkParameterIsNotNull(bytes, "bytes");
        try {
            byte[] buffer = new byte[8192];
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            DigestInputStream dis = null;
            try {
                dis = new DigestInputStream(new ByteArrayInputStream(bytes, start, length), md);
                while (dis.read(buffer) != -1) ;
            } catch (IOException e) {
            } finally {
                dis.close();
            }
            String md5 = new BigInteger(1, md.digest()).toString(16);
            while (md5.length() < 32) {
                md5 = '0' + md5;
            }
            return md5;
        } catch (Exception e) {
            return "";
        }
    }

    /**
     * 获取文件MD5
     *
     * @param file 文件
     * @return MD5
     */
    public static final String getFileMd5String(@NotNull String file) {
        Intrinsics.checkParameterIsNotNull(file, "file");
        File contentFile = new File(file);
        DigestInputStream inputStream = null;
        try {
            byte[] buffer = new byte[8 * 1024];
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            inputStream = new DigestInputStream(new FileInputStream(contentFile), md);

            while (true) {
                if (inputStream.read(buffer) == -1) {
                    break;
                }
            }
            inputStream.close();
            String md5 = new BigInteger(1, md.digest()).toString(16);
            while (md5.length() < 32) {
                md5 = '0' + md5;
            }
            return md5;
        } catch (Exception e) {
            return null;
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
            }
        }
    }

    /**
     * 是否支持并行下载
     *
     * @param code code
     * @param headers 头部
     * @return boolean
     */
    public static final boolean isParallelDownloadingSupported(int code, @NotNull Map headers) {
        Intrinsics.checkParameterIsNotNull(headers, "headers");
        return acceptRanges(code, headers);
    }

    /**
     * 获取请求支持的文件下载类型
     *
     * @param request 请求
     * @param downloader 回调
     * @return 数据类型集合
     */
    public static final Set<Downloader.FileDownloaderType> getRequestSupportedFileDownloaderTypes(@NotNull Downloader.ServerRequest request, @NotNull Downloader downloader) {
        Intrinsics.checkParameterIsNotNull(request, "request");
        Intrinsics.checkParameterIsNotNull(downloader, "downloader");
        Set fileDownloaderTypeSet = new LinkedHashSet(Arrays.asList(new Downloader.FileDownloaderType[]{Downloader.FileDownloaderType.SEQUENTIAL}));
        try {
            Downloader.Response response = downloader.execute(request, getSimpleInterruptMonitor());
            if (response != null) {
                if (isParallelDownloadingSupported(response.getCode(), response.getResponseHeaders())) {
                    fileDownloaderTypeSet.add(Downloader.FileDownloaderType.PARALLEL);
                }
                downloader.disconnect(response);
            }
            return fileDownloaderTypeSet;
        } catch (Exception e) {
            return fileDownloaderTypeSet;
        }
    }

    /**
     * 是否在范围
     *
     * @param code code
     * @param headers 头部数据
     * @return Ranges
     */
    public static final boolean acceptRanges(int code, @NotNull Map headers) {
        String acceptRangeValue = getHeaderValue(headers, "Accept-Ranges", "accept-ranges", "AcceptRanges");
        String transferValue = getHeaderValue(headers, "Transfer-Encoding", "transfer-encoding", "TransferEncoding");
        long contentLength = getContentLengthFromHeader(headers, -1L);
        boolean acceptsRanges = code == HttpURLConnection.HTTP_PARTIAL || acceptRangeValue == "bytes";
        return (contentLength > -1L && acceptsRanges) || (contentLength > -1L && transferValue != null && transferValue.toLowerCase() != "chunked");
    }

    /**
     * 获取头部内容长度
     *
     * @param headers 头部
     * @param defaultValue 默认值
     * @return 长度
     */
    public static final long getContentLengthFromHeader(Map<String, List<String>> headers, long defaultValue) {
        String contentRange = getHeaderValue(
            headers,
            HEADER_CONTENT_RANGE,
            HEADER_CONTENT_RANGE_LEGACY,
            HEADER_CONTENT_RANGE_COMPAT
        );
        Integer lastIndexOf = TextTool.isNullOrEmpty(contentRange) ? -1 : contentRange.lastIndexOf('/');
        Long contentLength = -1L;
        if (lastIndexOf != null && lastIndexOf != -1 && lastIndexOf < contentRange.length()) {
            if (contentRange == null) {
                throw new NullPointerException("null cannot be cast to non-null type java.lang.String");
            }
            String var = contentRange.substring(lastIndexOf + 1);
            contentLength = toLongOrNull(var);
            contentLength = contentLength != null ? contentLength : -1L;
        }
        if (contentLength == -1L) {
            String head = getHeaderValue(
                headers,
                HEADER_CONTENT_LENGTH,
                HEADER_CONTENT_LENGTH_LEGACY,
                HEADER_CONTENT_LENGTH_COMPAT
            );
            contentLength = toLongOrNull(head);
            contentLength = contentLength != null ? contentLength : defaultValue;
        }
        return contentLength;
    }

    /**
     * 获取key数据
     *
     * @param headers 头部
     * @param keys key
     * @return 数据
     */
    public static String getHeaderValue(@NotNull Map<String, List<String>> headers, @NotNull String... keys) {
        String[] cacheKeys = keys;
        int keyLength = keys.length;
        for (int i = 0; i < keyLength; ++i) {
            String key = cacheKeys[i];
            List<String> list = headers.get(key);
            String value = null;
            if (list != null && !TextTool.isNullOrEmpty(list.get(0))) {
                value = list.get(0);
            }
            if (value != null && value.length() > 0 && !isWhitespace(value)) {
                return value;
            }
        }
        return null;
    }

    /**
     * 获取请求内容长度
     *
     * @param request 请求
     * @param downloader 回调
     * @return 长度
     */
    public static final long getRequestContentLength(Downloader.ServerRequest request, Downloader downloader) {
        try {
            Downloader.Response response = downloader.execute(request, getSimpleInterruptMonitor());
            Map headers = Collections.emptyMap();
            if (response != null && response.getResponseHeaders() != null) {
                headers = response.getResponseHeaders();
            }
            long contentLength = getContentLengthFromHeader(headers, -1L);
            if (response != null) {
                downloader.disconnect(response);
            }
            return contentLength;
        } catch (Exception e) {
            return -1L;
        }
    }

    /**
     * 是否uri路径
     *
     * @param path 路径
     * @return boolean
     */
    public static final boolean isUriPath(@NotNull String path) {
        String value = path.length() > 0 ? path : null;
        boolean isUri;
        if (value != null) {
            isUri = path.startsWith("content://") || path.startsWith("file://");
        } else {
            isUri = false;
        }
        return isUri;
    }

    /**
     * 获取文件uri
     *
     * @param path 路径
     * @return uri
     */
    public static final Uri getFileUri(@NotNull String path) {
        Uri uri;
        if (isUriPath(path)) {
            uri = Uri.parse(path);
        } else {
            uri = Uri.getUriFromFile(new File(path));
        }
        return uri;
    }

    /**
     * 删除文件
     *
     * @param file 文件
     * @return boolean
     */
    public static final boolean deleteFile(File file) {
        try {
            Files.delete(file.toPath());
        } catch (IOException e) {
            return false;
        }
        return true;
    }

    /**
     * 修改文件名称
     *
     * @param oldFile 旧文件
     * @param newFile 新文件
     * @return boolean
     */
    public static final boolean renameFile(@NotNull File oldFile, @NotNull File newFile) {
        return oldFile.renameTo(newFile);
    }

    /**
     * 复制响应
     *
     * @param response 响应
     * @return 响应
     */
    @NotNull
    public static final Downloader.Response copyDownloadResponseNoStream(@NotNull Downloader.Response response) {
        return new Downloader.Response(response.getCode(), response.isSuccessful(), response.getContentLength(), (InputStream) null, response.getRequest(), response.getHash(), response.getResponseHeaders(), response.getAcceptsRanges(), response.getErrorResponse());
    }

    /**
     * 获取CookieManager
     *
     * @return CookieManager
     */
    @NotNull
    public static final CookieManager getDefaultCookieManager() {
        CookieManager cookieManager = new CookieManager();
        cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ALL);
        return cookieManager;
    }

    /**
     * 判断允许时间是否已过期
     *
     * @param timeStartedMillis 开始毫秒时间
     * @param timeStopMillis 停止毫秒时间
     * @param allowedTimeMillis 允许毫秒时间
     * @return 允许时间是否已过期
     */
    public static final boolean hasAllowedTimeExpired(long timeStartedMillis, long timeStopMillis, long allowedTimeMillis) {
        return timeStopMillis - timeStartedMillis >= allowedTimeMillis;
    }

    /**
     * 获取引用url
     *
     * @param url 链接
     * @return 格式链接
     */
    public static final String getRefererFromUrl(String url) {
        try {
            Uri uri = Uri.parse(url);
            StringBuilder stringBuilder = new StringBuilder();
            return stringBuilder.append(uri.getScheme()).append("://").append(uri.getDecodedAuthority()).toString();
        } catch (Exception e) {
            return REFERER_DEFAULT;
        }
    }

    /**
     * 将流转成字符串
     *
     * @param inputStream 输入流
     * @return 流字符串
     */
    public static final String copyStreamToString(@Nullable InputStream inputStream) {
        return copyStreamToString(inputStream, true);
    }

    /**
     * 将流转成字符串
     *
     * @param inputStream 输入流
     * @param closeStream 关闭流
     * @return 流字符串
     */
    public static final String copyStreamToString(@Nullable InputStream inputStream, Boolean closeStream) {
        if (inputStream == null) {
            return "";
        } else {
            BufferedReader bufferedReader = null;
            try {
                bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                StringBuilder stringBuilder = new StringBuilder();
                String line = bufferedReader.readLine();
                while (line != null) {
                    stringBuilder.append(line)
                        .append('\n');
                    line = bufferedReader.readLine();
                }
                return stringBuilder.toString();
            } catch (Exception e) {
                return "";
            } finally {
                if (closeStream) {
                    try {
                        if (bufferedReader != null) {
                            bufferedReader.close();
                        }
                    } catch (IOException e) {
                    }
                }
            }
        }
    }

    /**
     * 是否空白字符
     *
     * @param value 值
     * @return boolean
     */
    private static boolean isWhitespace(String value) {
        for (int i = 1; i < value.length(); ++i) {
            char v = value.charAt(i);
            if (Character.isWhitespace(v) || Character.isSpaceChar(v)) {
                return true;
            }
        }
        return false;
    }

    /**
     * 转成Long
     *
     * @param value 值
     * @return long
     */
    private static Long toLongOrNull(String value) {
        int length = value.length();
        if (length == 0) return null;
        int radix = 10;
        int start;
        boolean isNegative;
        long limit;
        char firstChar = value.charAt(0);
        if (firstChar < '0') { //Possible leading sign
            if (length == 1) return null; // non-digit (possible sign) only, no digits after
            start = 1;
            if (firstChar == '-') {
                isNegative = true;
                limit = Long.MIN_VALUE;
            } else if (firstChar == '+') {
                isNegative = false;
                limit = -Long.MAX_VALUE;
            } else
                return null;
        } else {
            start = 0;
            isNegative = false;
            limit = -Long.MAX_VALUE;
        }
        long limitForMaxRadix = (-Long.MAX_VALUE) / 36;
        long limitBeforeMul = limitForMaxRadix;
        long result = 0L;
        for (int i = start; i < length; ++i) {
            int digit = Character.digit(value.charAt(i), radix);
            if (digit < 0) return null;
            if (result < limitBeforeMul) {
                if (limitBeforeMul == limitForMaxRadix) {
                    limitBeforeMul = limit / radix;
                    if (result < limitBeforeMul) {
                        return null;
                    }
                } else {
                    return null;
                }
            }
            result *= radix;
            if (result < limit + digit) return null;
            result -= digit;
        }
        return isNegative ? result : -result;
    }

    /**
     * 获取扩展名
     *
     * @param name 名称
     * @return 扩展名
     */
    private static String getNameWithoutExtension(String name) {
        int index = name.lastIndexOf('.');
        return index == -1 ? "" : name.substring(0, index);
    }

    /**
     * 获取简单中断监视器
     *
     * @return boolean
     */
    @NotNull
    public static final InterruptMonitor getSimpleInterruptMonitor() {
        return (InterruptMonitor) (new InterruptMonitor() {
            public boolean isInterrupted() {
                return false;
            }
        });
    }
}
